package com.shop.zzh.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: zzh
 * @description: jwt工具类
 * @author: zhuzh
 * @create: 2019-12-16 22:03
 **/
@Component
public class JwtTokenUtil {
	private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
	private static final String CLAIM_KEY_USERNAME = "sub";
	private static final String CLAIM_KEY_CREATED = "created";
	@Value("${jwt.secret}")
	private String secret;
	@Value("${jwt.expiration}")
	private Long expiration;
	@Value("${jwt.tokenHead}")
	private String tokenHead;

	/**
	 * 根据负责生成JWT的token
	 */
	private String generateToken(Map<String, Object> claims) {
		return Jwts.builder()
				.setClaims(claims)
				.setExpiration(generateExpirationDate())
				.signWith(SignatureAlgorithm.HS512, secret)
				.compact();
	}

	/**
	 * 从token中获取JWT中的负载
	 */
	private Claims getClaimsFromToken(String token) {
		Claims claims = null;
		try {
			claims = Jwts.parser()
					.setSigningKey(secret)
					.parseClaimsJws(token)
					.getBody();
		} catch (Exception e) {
			LOGGER.info("JWT格式验证失败:{}",token);
		}
		return claims;
	}

	/**
	 * 生成token的过期时间
	 */
	private Date generateExpirationDate() {
		return new Date(System.currentTimeMillis() + expiration * 1000);
	}

	/**
	 * 从token中获取登录用户名
	 */
	public String getUserNameFromToken(String token) {
		String username;
		try {
			Claims claims = getClaimsFromToken(token);
			username =  claims.getSubject();
		} catch (Exception e) {
			username = null;
		}
		return username;
	}

	/**
	 * 验证token是否还有效
	 *
	 * @param token       客户端传入的token
	 * @param userDetails 从数据库中查询出来的用户信息
	 */
	public boolean validateToken(String token, UserDetails userDetails) {
		String username = getUserNameFromToken(token);
		return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
	}

	/**
	 * 判断token是否已经失效
	 */
	private boolean isTokenExpired(String token) {
		Date expiredDate = getExpiredDateFromToken(token);
		return expiredDate.before(new Date());
	}

	/**
	 * 从token中获取过期时间
	 */
	private Date getExpiredDateFromToken(String token) {
		Claims claims = getClaimsFromToken(token);
		return claims.getExpiration();
	}

	/**
	 * 根据用户信息生成token
	 */
	public String generateToken(UserDetails userDetails) {
		Map<String, Object> claims = new HashMap<>();
		claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
		claims.put(CLAIM_KEY_CREATED, new Date());
		return generateToken(claims);
	}

	/**
	 * 当原来的token没过期时是可以刷新的
	 *
	 * @param oldToken 带tokenHead的token
	 */
	public String refreshHeadToken(String oldToken) {
		if(StrUtil.isEmpty(oldToken)){
			return null;
		}
		String token = oldToken.substring(tokenHead.length());
		if(StrUtil.isEmpty(token)){
			return null;
		}
		//token校验不通过
		Claims claims = getClaimsFromToken(token);
		if(claims==null){
			return null;
		}
		//如果token已经过期，不支持刷新
		if(isTokenExpired(token)){
			return null;
		}
		//如果token在30分钟之内刚刷新过，返回原token
		if(tokenRefreshJustBefore(token,30*60)){
			return token;
		}else{
			claims.put(CLAIM_KEY_CREATED, new Date());
			return generateToken(claims);
		}
	}

	/**
	 * 判断token在指定时间内是否刚刚刷新过
	 * @param token 原token
	 * @param time 指定时间（秒）
	 */
	private boolean tokenRefreshJustBefore(String token, int time) {
		Claims claims = getClaimsFromToken(token);
		Date created = claims.get(CLAIM_KEY_CREATED, Date.class);
		Date refreshDate = new Date();
		//刷新时间在创建时间的指定时间内
		if(refreshDate.after(created)&&refreshDate.before(DateUtil.offsetSecond(created,time))){
			return true;
		}
		return false;
	}


	/**
	 * 判断token是否可以被刷新
	 */
	public boolean canRefresh(String token) {
		return !isTokenExpired(token);
	}

	/**
	 * 刷新token
	 */
	public String refreshToken(String token) {
		Claims claims = getClaimsFromToken(token);
		claims.put(CLAIM_KEY_CREATED, new Date());
		return generateToken(claims);
	}
}
